从循环到函数式:Racket 常用高阶函数详解

#Innolight #Lisp #Racket

在很多传统语言(如 C、Python、Java)中,我们习惯写:

而在 Racket 中,你会看到一种完全不同的风格:

同样的任务,用 map、filter、apply 等高阶函数轻松完成。
无需循环,也无需变量控制状态。

为什么会这样?
这种写法又好在哪里?
本文会从“思维方式的变化”开始,再详细介绍你必会的高阶函数。

🌱 什么是高阶函数?

一句话:

高阶函数 = 可以把函数当作参数传入的函数。

例如:

(map add1 '(1 2 3))

这里 add1 是“被传入 map 的参数”,就像普通值一样,非常自然。

🔷 map —— 对每个元素做转换

map 就是“把函数应用到列表的每个元素上”。

基本示例

(map add1 '(1 2 3))
;; '(2 3 4)

使用匿名函数

(map (λ (x) (* x x)) '(1 2 3 4))
;; '(1 4 9 16)

多列表并行 map

(map + '(1 2 3) '(10 20 30))
;; '(11 22 33)

🔷 filter —— 挑出满足条件的元素

(filter even? '(1 2 3 4 5 6))
;; '(2 4 6)

字符串示例:

(filter (λ (s) (> (string-length s) 3))
        '("hi" "hello" "abc" "rack"))
;; '("hello" "rack")

🔷 apply —— 把列表“展开”成函数参数

apply 用来执行“带可变参数”的函数非常好用。

求和

(apply + '(1 2 3 4))
;; 10

等价于:

(+ 1 2 3 4)

拼接多个列表

(apply append '((1 2) (3 4) (5 6)))
;; '(1 2 3 4 5 6)

动态参数(自动化脚本常用)

(define args '(1 2 3))
(apply format "~a ~a ~a" args)
;; "1 2 3"

🔷 foldl / foldr —— 把列表“压缩”为单个值

折叠是函数式编程的核心。

foldl(从左折叠)

(foldl + 0 '(1 2 3 4))
;; 10

其本质是:

(+ (+ (+ (+ 0 1) 2) 3) 4)

foldr(从右折叠)

常用于构造结构:

(foldr cons '() '(1 2 3))
;; '(1 2 3)

🔷 ormap / andmap —— 用逻辑判断整个列表

ormap

“只要一个满足条件,就返回 #t

(ormap even? '(1 3 5 6))
;; #t

andmap

“所有元素都满足条件,才返回 #t

(andmap number? '(1 2 3))
;; #t

🌟 为什么说“从循环到函数式”?

这是新手最常问的问题。
下面用最典型的例子说明。

✔ 传统命令式写法(像 C 语言)

(define (double-evens lst)
  (define acc '())
  (for ([x lst])
    (when (even? x)
      (set! acc (append acc (list (* x 2))))))
  acc)

问题:

  1. 要维护 acc
  2. 要写 set!
  3. append 是 O(n),循环里 append 会变 O(n²)
  4. 逻辑是“我怎么做”

✔ Racket / 函数式写法

(map (λ (x) (* x 2))
     (filter even? lst))

优势:

✔ 为什么这个风格更好?

1. 更容易读

读起来像自然语言:

“过滤偶数,再翻倍。”

而不是:

“我声明 acc 并遍历每个 x,符合条件时 append...”

2. 更少 bug

没有:

3. 更容易组合

高阶函数像积木:

(map square (map add1 (filter even? lst)))

功能组合清晰明确。

4. 更适合递归语言 Racket

Racket 的核心是:

高阶函数和这些特性完美契合。

🌟 常见组合技巧

1. map + filter

(map add1 (filter even? '(1 2 3 4 5 6)))
;; '(3 5 7)

2. apply + map(矩阵处理常用)

(apply map max '((1 9 3)
                 (4 2 6)
                 (7 5 0)))
;; '(7 9 6)

3. foldl 模拟 map(理解 fold 的好方法)

(foldl (λ (x acc) (cons (* x x) acc))
       '()
       '(1 2 3 4))
;; '(16 9 4 1)

🏁 总结

通过这篇文章,你已经学会:

这些高阶函数不仅能让你写出更优雅的 Racket 代码,也是走向:

的基础能力。